/**
 *    @file       crsa.cpp
 *
 *    @brief      
 *    @details
 *
 *    @version    1.0
 *    @date       01/24/2014 02:45:21 PM
 *
 *    @author     Theophilus (), wangtf418@gmail.com
 */
#include "crsa.h"
#include "cmd5.h"

#define RSA_ELEVEN 11
#define RSA_FORTY_TWO 42 

#define SSL_SIG_LENGTH 36
#define NID_md5_SIGN_LEN 99
#define NID_sha1_SIGN_LEN 102
#define NID_ripemd160_SIGN_LEN 102

crsa::crsa()
{

}

crsa::~crsa()
{

}

/**
 * 1.keylen is the expected bit length of generating keys , it's typically 64(512), 128(1024) or 256(2048). And 1024 is suggested.
 * 2.exp is the exponent used to generate keys in method , it must be an oddnum like 3, 17, 65537. And 3 is suggested.
 */
bf_int_t crsa::gen_key(bf_int_t keylen, bf_ulong_t exp, std::string &public_key_n, std::string &public_key_e, std::string &private_key)
{
	if (keylen <= 0 || (exp % 2 == 0))
	{
		return BF_ERROR;
	}
	RSA *rsa_key = RSA_generate_key(keylen * 8, exp, NULL, NULL);
	if(!rsa_key)
	{
		return BF_ERROR;
	}
	bf_char_t *pub_key_n, *pub_key_e, *pri_key;
   	pub_key_n = BN_bn2hex(rsa_key->n);
	pub_key_e = BN_bn2hex(rsa_key->e);
	pri_key = BN_bn2hex(rsa_key->d);
	if (!pub_key_n || !pub_key_e || !pri_key)
	{
		return BF_ERROR;
	}
	public_key_n = pub_key_n;
	public_key_e = pub_key_e;
	private_key = pri_key;
	free(pub_key_n);
	free(pub_key_e);
	free(pri_key);
	RSA_free(rsa_key);
	return BF_OK;
}

/**
 * If padding equal to RSA_PKCS1_PADDING or RSA_SSLV23_PADDING, the first parameter of RSA_public_encrypt must be less than RSA_size(rsa) - RSA_ELEVEN 
 * If padding equal to RSA_PKCS1_OAEP_PADDING , the first parameter of RSA_public_encrypt must be less than RSA_size(rsa) - RSA_FORTY_TWO
 * If padding equal to RSA_NO_PADDING , the first parameter of RSA_public_encrypt must be equal to RSA_size(rsa)
 */
bf_int_t crsa::public_encrypt(const std::string &public_key_n, const std::string &public_key_e,
		const std::string &text, std::string &text_out, bf_int_t padding /* = RSA_PKCS1_PADDING */)
{
	if(public_key_n.empty() || public_key_e.empty() || text.empty())
	{
		return BF_ERROR;
	}

	RSA *rsa = RSA_new();
	if (!rsa)
	{
		return BF_ERROR;
	}

	bf_int_t ret = BF_OK;
	const bf_int_t textlen = (bf_int_t)text.length();

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}
		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		bf_int_t max_section_flen = 0;

		const bf_int_t rsa_size = RSA_size(rsa);
		if(padding == RSA_PKCS1_PADDING || padding == RSA_SSLV23_PADDING)
		{
			max_section_flen = rsa_size - RSA_ELEVEN - 1;
		}
		else if (padding == RSA_PKCS1_OAEP_PADDING)
		{
			max_section_flen = rsa_size - RSA_FORTY_TWO - 1;
		}
		else if (padding == RSA_NO_PADDING)
		{
			max_section_flen = rsa_size;
		}
		else
		{
			ret = BF_ERROR;
			break;
		}
		
		if (max_section_flen <= 0)
		{
			return BF_ERROR;
		}
		bf_int_t signature_size = 0;
		bf_int_t offset = 0;
		bf_int_t section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
		const bf_uchar_t *cur_pos = (const bf_uchar_t*)text.c_str();
		bf_uchar_t *outbuf = new bf_uchar_t[rsa_size];
		text_out.clear();
		while(ret == BF_OK && section_flen > 0)
		{
			signature_size = RSA_public_encrypt(section_flen, cur_pos,  outbuf, rsa, padding);
			if (signature_size == -1)
			{
				ret = BF_ERROR;
				text_out.clear();			
			}			
			else
			{
				text_out.append((bf_char_t *)outbuf, signature_size);
			}
			offset += section_flen;
			cur_pos += section_flen;
			section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
		}
		delete [] outbuf;
		outbuf = NULL;
	} while (false);
	
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

bf_int_t crsa::private_decrypt(const std::string &public_key_n, const std::string &public_key_e, const std::string &private_key,
		const std::string &text, std::string &text_out, bf_int_t padding /* = RSA_PKCS1_PADDING */)
{
	if(public_key_n.empty() || public_key_e.empty() || private_key.empty() || text.empty())
	{
		return BF_ERROR;
	}

	RSA *rsa = RSA_new();
	if (!rsa)
	{
		return BF_ERROR;
	}

	bf_int_t ret = BF_OK;
	const bf_int_t textlen = (bf_int_t)text.length();

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->d, private_key.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		const bf_int_t section_flen = RSA_size(rsa);
		if(text.length() % section_flen != 0)
		{
			ret = BF_ERROR;
			break;
		}
		else
		{
			bf_int_t signature_size = 0;
			bf_int_t offset = 0;
			bf_uchar_t *cur_pos = (bf_uchar_t *)text.c_str();
			bf_uchar_t *outbuf =  new bf_uchar_t[section_flen];
			text_out.clear();
			while(ret == BF_OK && (textlen - offset) > 0)
			{
				signature_size = RSA_private_decrypt(section_flen, cur_pos, outbuf, rsa, padding);
				if (signature_size == -1)
				{
					ret = BF_ERROR;
					text_out.clear();			
				}			
				else
				{
					text_out.append((bf_char_t *)outbuf, signature_size);
				}
				offset += section_flen;
				cur_pos += section_flen;
			}
			delete [] outbuf;
			outbuf = NULL;
		}

	} while (false);
		
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

/**
 * padding must be RSA_PKCS1_PADDING or RSA_NO_PADDING, and RSA_PKCS1_PADDING is suggested.
 * If padding == RSA_NO_PADDING,the length of text must be multiples of RSA_size(pRas)
 * When generating or verifying PKCS #1 signatures, RSA_sign(3) and RSA_verify(3) should be used.
 */
bf_int_t crsa::private_encrypt(const std::string &public_key_n, const std::string &public_key_e, const std::string &private_key,
		const std::string &text, std::string &text_out, bf_int_t padding /* = RSA_PKCS1_PADDING */)
{
	if(public_key_n.empty() || public_key_e.empty() || private_key.empty() || text.empty())
	{
		return BF_ERROR;
	}

	if(padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING)
	{
		return BF_ERROR;
	}

	RSA *rsa = RSA_new();
	if (!rsa)
	{
		return BF_ERROR;
	}

	bf_int_t ret = BF_OK;
	const bf_int_t textlen = (bf_int_t)text.length();

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->d, private_key.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		bf_int_t max_section_flen = 0;

		const bf_int_t rsa_size = RSA_size(rsa);
		if(padding == RSA_PKCS1_PADDING)
		{
			max_section_flen = rsa_size - RSA_ELEVEN - 1;
		}
		else if (padding == RSA_NO_PADDING)
		{
			max_section_flen = rsa_size;
			if(text.length() % rsa_size != 0)
			{
				ret = BF_ERROR;
				break;
			}
		}			
		else
		{
			ret = BF_ERROR;
			break;
		}

		if (max_section_flen <= 0)
		{
			return BF_ERROR;
		}

		bf_int_t signature_size = 0;
		bf_int_t offset = 0;
		bf_int_t section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
		const bf_uchar_t *cur_pos = (const bf_uchar_t*)text.c_str();
		bf_uchar_t *outbuf = new bf_uchar_t[rsa_size];
		text_out.clear();
		while(ret == BF_OK && section_flen > 0)
		{
			signature_size = RSA_private_encrypt(section_flen, cur_pos,  outbuf, rsa, padding);
			if (signature_size == -1)
			{
				ret = BF_ERROR;
				text_out.clear();			
			}			
			else
			{
				text_out.append((bf_char_t *)outbuf, signature_size);
			}	
			offset += section_flen;
			cur_pos += section_flen;
			section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
		}
		delete [] outbuf;
		outbuf = NULL;
	} while (false);
	
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

/**
 * padding must be RSA_PKCS1_PADDING or RSA_NO_PADDING, and it must be the mode that was used to encrypt the data.
 * When generating or verifying PKCS #1 signatures, RSA_sign(3) and RSA_verify(3) should be used.
 */
bf_int_t crsa::public_decrypt(const std::string &public_key_n, const std::string &public_key_e,
		const std::string &text, std::string &text_out, bf_int_t padding /* = RSA_PKCS1_PADDING */)
{
	if(public_key_n.empty() || public_key_e.empty() || text.empty())
	{
		return BF_ERROR;
	}

	if(padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING)
	{
		return BF_ERROR;
	}

	RSA *rsa = RSA_new();
	if (!rsa)
	{
		return BF_ERROR;
	}

	bf_int_t ret = BF_OK;
	const bf_int_t textlen = (bf_int_t)text.length();

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		bf_int_t signature_size = 0;
		bf_int_t offset = 0;
		const bf_uchar_t *cur_pos = (const bf_uchar_t*)text.c_str();
		text_out.clear();
		const bf_int_t section_flen = RSA_size(rsa);
		if(text.length() % section_flen != 0)
		{
			ret = BF_ERROR;
			break;
		}
		bf_uchar_t *outbuf = new bf_uchar_t[section_flen];		//the buff len must be longer than ( RSA_size( rsa ) - RSA_ELEVEN )

		while(ret == BF_OK && (textlen - offset) > 0)
		{
			signature_size = RSA_public_decrypt(section_flen, cur_pos, outbuf, rsa, padding);
			if (signature_size == -1)
			{
				ret = BF_ERROR;
				text_out.clear();			
			}			
			else
			{
				text_out.append((bf_char_t*)outbuf, signature_size);
			}
			offset += section_flen;
			cur_pos += section_flen;
		}
		delete [] outbuf;
		outbuf = NULL;
	} while (false);
	
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

/** 
 * type must be NID_md5_sha1, NID_md5, NID_sha1 ,or NID_ripemd160 
 * If type == NID_md5_sha1, text.length() must multiples of SSL_SIG_LENGTH(36), and SSL_SIG_LENGTH is suggested
 * If type == NID_md5, text.length() is suggested no more than 99
 * If type == NID_sha1, text.length() is suggested no mmore 102
 * If type == NID_ripemd160, text.length() is suggested no mmore 102
 */
bf_int_t crsa::sign(const std::string &public_key_n, const std::string &public_key_e, const std::string &private_key,
		const std::string &text, std::string &sign_out, bf_int_t type /* = NID_md5 */)
{
	if(public_key_n.empty() || public_key_e.empty() || private_key.empty() || text.empty())
	{
		return BF_ERROR;
	}
	int ret = 0;
	int max_section_flen = 0;
	const bf_int_t textlen = (bf_int_t)text.length();
	if(type == NID_md5_sha1)
	{
		if(textlen % SSL_SIG_LENGTH != 0)
		{
			return BF_ERROR;
		}
		max_section_flen = SSL_SIG_LENGTH;
	}
	else if(type == NID_md5)
	{
		max_section_flen = NID_md5_SIGN_LEN;
	}
	else if(type == NID_sha1)
	{
		max_section_flen = NID_sha1_SIGN_LEN;
	}
	else if(type == NID_ripemd160)
	{
		max_section_flen = NID_ripemd160_SIGN_LEN;
	}

	if (max_section_flen <= 0)
	{
		return BF_ERROR;
	}
	
	RSA *rsa = RSA_new();
	if(!rsa)
	{
		return BF_ERROR;
	}

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->d, private_key.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		const bf_int_t rsa_size = RSA_size(rsa);
		bf_uchar_t *sign_ret = new bf_uchar_t[rsa_size];
		bf_uint_t signlen = 0;
		bf_int_t offset = 0;
		bf_int_t section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
		const bf_uchar_t *cur_pos = (const bf_uchar_t*)text.c_str();

		sign_out.clear();
		while(ret == BF_OK && section_flen > 0)
		{
			if(RSA_sign(type, cur_pos, section_flen, sign_ret, &signlen, rsa) == 0)
			{
				ret = BF_ERROR;
			}
			else
			{
				sign_out.append((bf_char_t*)sign_ret, signlen);
				offset += section_flen;
				cur_pos += section_flen;
				section_flen = ((textlen - offset) >= max_section_flen)? max_section_flen:(textlen - offset);
			}
		}		
		delete [] sign_ret;
		sign_ret = NULL;
	} while (false);
	
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

bf_int_t crsa::verify(const std::string &public_key_n, const std::string &public_key_e,
		const std::string &text, const std::string &sign, bf_int_t type /* = NID_md5 */)
{
	if(public_key_n.empty() || public_key_e.empty() || text.empty() || sign.empty())
	{
		return BF_ERROR;
	}

	bf_int_t ret = BF_OK;
	const bf_int_t textlen = (bf_int_t)text.length();
	const bf_int_t signlen = (bf_int_t)sign.length();
	int max_section_flen = 0;

	if(type == NID_md5_sha1)
	{
		max_section_flen = SSL_SIG_LENGTH;
	}
	else if(type == NID_md5)
	{
		max_section_flen = NID_md5_SIGN_LEN;
	}
	else if(type == NID_sha1)
	{
		max_section_flen = NID_sha1_SIGN_LEN;
	}
	else if(type == NID_ripemd160)
	{
		max_section_flen = NID_ripemd160_SIGN_LEN;
	}

	if (max_section_flen <= 0)
	{
		return BF_ERROR;
	}

	RSA *rsa = RSA_new();
	if(!rsa)
	{
		return BF_ERROR;
	}

	do 
	{
		if(BN_hex2bn(&rsa->n, public_key_n.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		if(BN_hex2bn(&rsa->e, public_key_e.c_str()) == 0)
		{
			ret = BF_ERROR;
			break;
		}

		const bf_int_t rsa_size = RSA_size(rsa);
		if(sign.length() % rsa_size != 0)
		{
			ret = BF_ERROR;
			break;
		}
		else
		{
			bf_uchar_t *signbuf = new bf_uchar_t[sign.length()];
			bf_uchar_t *textbuf = new bf_uchar_t[text.length()];
			memcpy(signbuf, sign.c_str(), sign.length());
			memcpy(textbuf, text.c_str(), text.length());
			int textoffset = 0;
			int signoffset = 0;
			int section_flen = ((textlen - textoffset) >= max_section_flen)? max_section_flen:(textlen - textoffset);

			while(ret == BF_OK && (textlen - textoffset) > 0 && (signlen - signoffset) > 0)
			{
				if(RSA_verify(type, textbuf + textoffset, section_flen, signbuf + signoffset, rsa_size, rsa) == 0)
				{
					ret = BF_ERROR;
				}
				else
				{
					textoffset += section_flen;
					signoffset += rsa_size;
					section_flen = ((textlen - textoffset) >= max_section_flen)? max_section_flen:(textlen - textoffset);
					if((textlen - textoffset)*(signlen - signoffset) == 0 && (textlen - textoffset) != (signlen - signoffset))
					{
						ret = BF_ERROR;
					}
				}

			}
			delete [] textbuf;
			delete [] signbuf ;
			textbuf = NULL;
			signbuf = NULL;
		}
	} while (false);
	
	RSA_free(rsa);
	rsa = NULL;
	return ret;
}

bf_int_t crsa::md5_sign(const std::string &public_key_n, const std::string &public_key_e, const std::string &private_key,
		const std::string &text, std::string &sign_out)
{
	bf_uchar_t hash[BF_MD5_HASH_LEN] = {0};
	cmd5::md5((bf_uchar_t*)text.c_str(), text.length(), hash);

	std::string strmd5;
	strmd5.append((bf_char_t*)hash, BF_MD5_HASH_LEN);
	
	return private_encrypt(public_key_n, public_key_e, private_key, strmd5, sign_out);	
}

bf_int_t crsa::md5_verify(const std::string &public_key_n, const std::string &public_key_e,
		const std::string &text, const std::string &sign)
{

	std::string strmd5;
	bf_uchar_t hash[BF_MD5_HASH_LEN] = {0};
	cmd5::md5((bf_uchar_t*)text.c_str(), text.length(), hash);
	strmd5.append((bf_char_t*)hash, BF_MD5_HASH_LEN);

	std::string sign_md5;
	bf_int_t ret = public_decrypt(public_key_n, public_key_e, sign, sign_md5);
	if(ret == BF_ERROR)
	{
		return BF_ERROR;
	}
	else if (strmd5 != sign_md5)
	{
		return BF_ERROR;
	}
	return BF_OK;
}

